#include "helper.h"
#include "monitor.h"
#include "reg.h"

extern uint32_t instr;
extern char assembly[80];

/* decode I-type instrucion with unsigned immediate */
static void decode_imm_type(uint32_t instr) {

	op_src1->type = OP_TYPE_REG;
	op_src1->reg = (instr & RS_MASK) >> (RT_SIZE + IMM_SIZE);
	op_src1->val = reg_w(op_src1->reg);
	
	op_src2->type = OP_TYPE_IMM;
	op_src2->imm = instr & IMM_MASK;
	op_src2->val = op_src2->imm;

	op_dest->type = OP_TYPE_REG;
	op_dest->reg = (instr & RT_MASK) >> (IMM_SIZE);
	op_dest->val = reg_w(op_dest->reg);
}

make_helper(lui) {

	decode_imm_type(instr);
	reg_w(op_dest->reg) = (op_src2->val << 16);
	sprintf(assembly, "lui   %s,   0x%04x", REG_NAME(op_dest->reg), op_src2->imm);
}

make_helper(ori) {

	decode_imm_type(instr);
	reg_w(op_dest->reg) = op_src1->val | op_src2->val;
	sprintf(assembly, "ori   %s,   %s,   0x%04x", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), op_src2->imm);
}

make_helper(andi) {

	decode_imm_type(instr);
	reg_w(op_dest->reg) = op_src1->val & op_src2->val;
	sprintf(assembly, "andi   %s,   %s,   0x%04x", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), op_src2->imm);
}

make_helper(xori) {

	decode_imm_type(instr);
	reg_w(op_dest->reg) = op_src1->val ^ op_src2->val;
	sprintf(assembly, "xori   %s,   %s,   0x%04x", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), op_src2->imm);
}

make_helper(addi) {

	decode_imm_type(instr);
	int temp = op_src2->val;
	if(temp >= 0x8000) temp -= 0x10000;

	temp += op_src1->val;
	int sgn_1 = op_src1->val >> 31;
	int sgn_2 = op_src2->val >> 31;
	int sgn_t = temp >> 31;
	int str_offset = 0;

	if(sgn_1 == sgn_2 && sgn_1 != sgn_t)  // ov exception
	{
		cp0_w(R_CAUSE) = (cp0_w(R_CAUSE) & (~EXCCODE_MASK)) | (Ov << 2);
		cp0_w(R_EPC) = cpu.pc;
		cp0_w(R_STATUS) = cp0_w(R_STATUS) | EXL_MASK;
		if(temu_state == JUMP)	// delay slot
		{
			cp0_w(R_CAUSE) = cp0_w(R_CAUSE) | BD_MASK;
			cp0_w(R_EPC) -= 4;
		}
		str_offset = sprintf(assembly, "Ov exception occured.\n");
	}
	reg_w(op_dest->reg) = temp;
	sprintf(assembly + str_offset, "addi   %s,   %s,   0x%04x", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), op_src2->val);
}

make_helper(addiu) {

	decode_imm_type(instr);
	reg_w(op_dest->reg) = op_src1->val + op_src2->val;
	sprintf(assembly, "addiu   %s,   %s,   0x%04x", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), op_src2->val);
}

make_helper(slti) {

	decode_imm_type(instr);
	int temp = op_src2->val;
	if(temp >= 0x8000) temp -= 0x10000;
	reg_w(op_dest->reg) =  ((int)op_src1->val) < temp;
	sprintf(assembly, "slti   %s,   %s,   0x%04x", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), op_src2->val);
}

make_helper(sltiu) {

	decode_imm_type(instr);
	reg_w(op_dest->reg) = op_src1->val < op_src2->val;
	sprintf(assembly, "sltiu   %s,   %s,   0x%04x", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), op_src2->val);
}

make_helper(beq) {

	decode_imm_type(instr);
	int offset = (op_src1->val == op_dest->val) ? op_src2->val : 0;
	cpu.next_pc = (offset == 0) ? cpu.pc : cpu.pc + (offset << 2);
	sprintf(assembly, "beq   %s,   %s,   %d", REG_NAME(op_src1->reg), REG_NAME(op_dest->reg), op_src2->val);
	temu_state = JUMP;
}

make_helper(bne) {

	decode_imm_type(instr);
	int offset = (op_src1->val != op_dest->val) ? op_src2->val : 0;
	cpu.next_pc = (offset == 0) ? cpu.pc : cpu.pc + (offset << 2);
	sprintf(assembly, "bne   %s,   %s,   %d", REG_NAME(op_src1->reg),  REG_NAME(op_dest->reg), op_src2->val);
	temu_state = JUMP;
}

make_helper(bgtz) {

	decode_imm_type(instr);
	int offset = (((~op_src1->val + 1) >> 31) != 0) ? op_src2->val : 0;
	cpu.next_pc = (offset == 0) ? cpu.pc : cpu.pc + (offset << 2);
	sprintf(assembly, "bgtz   %s,   %d", REG_NAME(op_src1->reg),  op_src2->val);
	temu_state = JUMP;
}

make_helper(blez) {

	decode_imm_type(instr);
	int offset = (((~op_src1->val + 1) >> 31) == 0) ? op_src2->val : 0;
	cpu.next_pc = (offset == 0) ? cpu.pc : cpu.pc + (offset << 2);
	sprintf(assembly, "blez  %s,   %d", REG_NAME(op_src1->reg),  op_src2->val);
	temu_state = JUMP;
}

make_helper(bz) {	// 包含bltz、bgez、bltzal、bgezal

	decode_imm_type(instr);
	int offset = 0;
	
	switch (op_dest->reg)
	{
	case 0x0:
		offset = ((op_src1->val >> 31) != 0) ? op_src2->val : 0;
		cpu.next_pc = (offset == 0) ? cpu.pc : cpu.pc + (offset << 2);
		sprintf(assembly, "bltz   %s,   %d", REG_NAME(op_src1->reg),  op_src2->val);
		break;

	case 0x1:
		offset = ((op_src1->val >> 31) == 0) ? op_src2->val : 0;
		cpu.next_pc = (offset == 0) ? cpu.pc : cpu.pc + (offset << 2);
		sprintf(assembly, "bgez   %s,   %d", REG_NAME(op_src1->reg),  op_src2->val);
		break;

	case 0x10:
		offset = ((op_src1->val >> 31) != 0) ? op_src2->val : 0;
		reg_w(R_RA) = cpu.pc + 8;
		cpu.next_pc = (offset == 0) ? cpu.pc : cpu.pc + (offset << 2);
		sprintf(assembly, "bltzal   %s,   %d", REG_NAME(op_src1->reg),  op_src2->val);
		break;

	case 0x11:
		offset = ((op_src1->val >> 31) == 0) ? op_src2->val : 0;
		reg_w(R_RA) = cpu.pc + 8;
		cpu.next_pc = (offset == 0) ? cpu.pc : cpu.pc + (offset << 2);
		sprintf(assembly, "bgezal   %s,   %d", REG_NAME(op_src1->reg),  op_src2->val);
		break;

	default:
		assert(0);
		break;
	}

	temu_state = JUMP;
}

make_helper(lb)
{

	decode_imm_type(instr);
	int temp = op_src2->val;
	if(temp >= 0x8000) temp -= 0x10000;
	
	uint32_t addr = (uint32_t)(temp + op_src1->val);

	int mem_data = (int)mem_read(addr, 1);
	if(mem_data >= 0x80) mem_data -= 0x100;

	reg_w(op_dest->reg) = (uint32_t)mem_data;
	sprintf(assembly, "lb   %s,   %d(%s)", REG_NAME(op_dest->reg), temp, REG_NAME(op_src1->reg));
}

make_helper(lbu)
{

	decode_imm_type(instr);
	int temp = op_src2->val;
	if(temp >= 0x8000) temp -= 0x10000;
	
	uint32_t addr = (uint32_t)(temp + op_src1->val);

	reg_w(op_dest->reg) = mem_read(addr, 1);
	sprintf(assembly, "lbu   %s,   %d(%s)", REG_NAME(op_dest->reg), temp, REG_NAME(op_src1->reg));
}

make_helper(lh)
{

	decode_imm_type(instr);
	int temp = op_src2->val;
	if(temp >= 0x8000) temp -= 0x10000;
	
	uint32_t addr = (uint32_t)(temp + op_src1->val);

	if(addr & 0x1)  // ADEL exception
	{
		cp0_w(R_CAUSE) = (cp0_w(R_CAUSE) & (~EXCCODE_MASK)) | (ADEL << 2);
		cp0_w(R_EPC) = cpu.pc;
		cp0_w(R_STATUS) = cp0_w(R_STATUS) | EXL_MASK;
		if(temu_state == JUMP)	// delay slot
		{
			cp0_w(R_CAUSE) = cp0_w(R_CAUSE) | BD_MASK;
			cp0_w(R_EPC) -= 4;
		}
		sprintf(assembly, "ADEL exception occured. [lh   %s,   %d(%s)]", REG_NAME(op_dest->reg), temp, REG_NAME(op_src1->reg));
	}
	else
	{
		int mem_data = (int)mem_read(addr, 2);
		if(mem_data >= 0x8000) mem_data -= 0x10000;

		reg_w(op_dest->reg) = (uint32_t)mem_data;
		sprintf(assembly, "lh   %s,   %d(%s)", REG_NAME(op_dest->reg), temp, REG_NAME(op_src1->reg));
	}
}

make_helper(lhu)
{

	decode_imm_type(instr);
	int temp = op_src2->val;
	if(temp >= 0x8000) temp -= 0x10000;
	
	uint32_t addr = (uint32_t)(temp + op_src1->val);

	if(addr & 0x1)  // ADEL exception
	{
		cp0_w(R_CAUSE) = (cp0_w(R_CAUSE) & (~EXCCODE_MASK)) | (ADEL << 2);
		cp0_w(R_EPC) = cpu.pc;
		cp0_w(R_STATUS) = cp0_w(R_STATUS) | EXL_MASK;
		if(temu_state == JUMP)	// delay slot
		{
			cp0_w(R_CAUSE) = cp0_w(R_CAUSE) | BD_MASK;
			cp0_w(R_EPC) -= 4;
		}
		sprintf(assembly, "ADEL exception occured. [lhu   %s,   %d(%s)]", REG_NAME(op_dest->reg), temp, REG_NAME(op_src1->reg));
	}
	else
	{
		reg_w(op_dest->reg) = mem_read(addr, 2);
		sprintf(assembly, "lhu   %s,   %d(%s)", REG_NAME(op_dest->reg), temp, REG_NAME(op_src1->reg));
	}
}

make_helper(lw)
{

	decode_imm_type(instr);
	int temp = op_src2->val;
	if(temp >= 0x8000) temp -= 0x10000;
	
	uint32_t addr = (uint32_t)(temp + op_src1->val);

	if(addr & 0x3)  // ADEL exception
	{
		cp0_w(R_CAUSE) = (cp0_w(R_CAUSE) & (~EXCCODE_MASK)) | (ADEL << 2);
		cp0_w(R_EPC) = cpu.pc;
		cp0_w(R_STATUS) = cp0_w(R_STATUS) | EXL_MASK;
		if(temu_state == JUMP)	// delay slot
		{
			cp0_w(R_CAUSE) = cp0_w(R_CAUSE) | BD_MASK;
			cp0_w(R_EPC) -= 4;
		}
		sprintf(assembly, "ADEL exception occured. [lw   %s,   %d(%s)]", REG_NAME(op_dest->reg), temp, REG_NAME(op_src1->reg));
	}
	else
	{
		reg_w(op_dest->reg) = mem_read(addr, 4);
		sprintf(assembly, "lw   %s,   %d(%s)", REG_NAME(op_dest->reg), temp, REG_NAME(op_src1->reg));
	}
}

make_helper(sb)
{

	decode_imm_type(instr);
	int temp = op_src2->val;
	if(temp >= 0x8000) temp -= 0x10000;
	
	uint32_t addr = (uint32_t)(temp + op_src1->val);

	mem_write(addr, 1, reg_w(op_dest->reg));
	sprintf(assembly, "sb   %s,   %d(%s)", REG_NAME(op_dest->reg), temp, REG_NAME(op_src1->reg));
}

make_helper(sh)
{

	decode_imm_type(instr);
	int temp = op_src2->val;
	if(temp >= 0x8000) temp -= 0x10000;
	
	uint32_t addr = (uint32_t)(temp + op_src1->val);

	if(addr & 0x1)  // ADES exception
	{
		cp0_w(R_CAUSE) = (cp0_w(R_CAUSE) & (~EXCCODE_MASK)) | (ADES << 2);
		cp0_w(R_EPC) = cpu.pc;
		cp0_w(R_STATUS) = cp0_w(R_STATUS) | EXL_MASK;
		if(temu_state == JUMP)	// delay slot
		{
			cp0_w(R_CAUSE) = cp0_w(R_CAUSE) | BD_MASK;
			cp0_w(R_EPC) -= 4;
		}
		sprintf(assembly, "ADES exception occured. [sh   %s,   %d(%s)]", REG_NAME(op_dest->reg), temp, REG_NAME(op_src1->reg));
	}
	else
	{
		mem_write(addr, 2, reg_w(op_dest->reg));
		sprintf(assembly, "sh   %s,   %d(%s)", REG_NAME(op_dest->reg), temp, REG_NAME(op_src1->reg));
	}
}

make_helper(sw)
{

	decode_imm_type(instr);
	int temp = op_src2->val;
	if(temp >= 0x8000) temp -= 0x10000;
	
	uint32_t addr = (uint32_t)(temp + op_src1->val);

	if(addr & 0x3)  // ADES exception
	{
		cp0_w(R_CAUSE) = (cp0_w(R_CAUSE) & (~EXCCODE_MASK)) | (ADES << 2);
		cp0_w(R_EPC) = cpu.pc;
		cp0_w(R_STATUS) = cp0_w(R_STATUS) | EXL_MASK;
		if(temu_state == JUMP)	// delay slot
		{
			cp0_w(R_CAUSE) = cp0_w(R_CAUSE) | BD_MASK;
			cp0_w(R_EPC) -= 4;
		}
		sprintf(assembly, "ADES exception occured. [sw   %s,   %d(%s)]", REG_NAME(op_dest->reg), temp, REG_NAME(op_src1->reg));
	}
	else
	{
		mem_write(addr, 4, reg_w(op_dest->reg));
		sprintf(assembly, "sw   %s,   %d(%s)", REG_NAME(op_dest->reg), temp, REG_NAME(op_src1->reg));
	}
}